package br.academia.arq.dao;

import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.exception.ConstraintViolationException;

import br.academia.arq.erros.DAOException;
import br.academia.dominio.PersistDB;

/**
 * Classe que deve ser herdada por todos os DAOs.
 * Oferece um conjunto de operações para facilitar o tratamento dos DAOs
 */
public class GenericDAO{
	/**
	 * Constantes para as operações no banco
	 */
	protected static final int INSERIR = 1;
	protected static final int ATUALIZAR = 2;
	protected static final int REMOVER = 3;

	/**
	 * Variável para se criar uma transação com o banco
	 */
	protected Transaction tx;

	/**
	 * Apresenta um elemento atômico de cominicação com o banco
	 */
	private Session session;
	
	/**
	 * Nome do dao
	 */
	protected String daoName;
	
	public GenericDAO(){
		
	}
	
	/**
	 * Desassocia o objeto com a sessao
	 */
	public void detach(PersistDB p) throws DAOException {

		try {
			getSession().evict(p);
		} catch (HibernateException e) {
			throw new DAOException(e);
		}
	}
	
	/**
	 * Retorna a instância do session factory
	 * @return SessionFactory
	 */
	public SessionFactory getSF() {
		return DAOFactory.getInstance().sessionFactory();
	}
	
	@SuppressWarnings("unchecked")
	public <T extends PersistDB> T findByPrimaryKey(int id, Class<T> classe) throws DAOException {

		try {

			T obj = (T) getSession().get(classe, new Integer(id));

			return obj;

		} catch (Exception e) {
			e.printStackTrace();
			throw new DAOException(e);
		}finally{
			getSession().close();
		}
	}
	
	/**
	 * Busca todos os dados da tabela mapeada pela entidade passada
	 *
	 */
	@SuppressWarnings("unchecked")
	public <T> Collection<T> findAll(Class<T> classe) throws DAOException {
		String hql = "from " + classe.getName();
		
		return getSession().createQuery(hql).list();
	}
	
	/**
	 * Método para se obter uma sessao com o banco
	 * @return
	 * @throws DAOException
	 */
	public synchronized Session getSession() throws DAOException {
		if (session == null || !session.isOpen()) {
			try {
				session = getSF().openSession();
			} catch (HibernateException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
				throw new DAOException("Nao foi possivel abrir sessao: " + e.getMessage(), e);
			}
		}
		
		return session;
	}
	
	/**
	 * Método para se efetuar uma operação com o banco
	 * @param obj
	 * @param operacao
	 * @throws DAOException
	 */
	protected void changeOperation(PersistDB obj, int operacao) throws DAOException {
		try {
		
			tx = getSession().beginTransaction();
		
			switch (operacao) {
			case INSERIR:
				getSession().save(obj);
				break;
			case ATUALIZAR:
				getSession().update(obj);
				break;
			case REMOVER:
				getSession().delete(obj);
				break;
			}
			
			tx.commit();
		}catch (ConstraintViolationException e){
			tx.rollback();
			getSession().close();
			throw new DAOException("A operação selecionada não pode ser realizada, pois nossa base de dados perderia sua consistência.", e);
		}catch (Exception e) {
			tx.rollback();
			getSession().close();
			throw new DAOException(e);
		}
	}
	
	/**
	 * Método para efetuar a inserção de um objeto mapedo no banco
	 * @param obj
	 * @throws DAOException
	 */
	public void save(PersistDB obj) throws DAOException{
		changeOperation(obj, INSERIR);
	}
	
	/**
	 * Método para efetuar a atualização de um objeto mapedo no banco
	 * @param obj
	 * @throws DAOException
	 */
	public void update(PersistDB obj) throws DAOException{
		changeOperation(obj, ATUALIZAR);
	}
	
	/**
	 * Método para efetuar a remoção de um objeto mapedo no banco
	 * @param obj
	 * @throws DAOException
	 */
	public void delete(PersistDB obj) throws DAOException{
		try{
			changeOperation(obj, REMOVER);
		}catch(ConstraintViolationException e){
			throw new DAOException("Não é possível realizar a remoção," +
					" pois existem referências ao item selecionado no nosso banco de dados.");
		}
	}
	
	/**
	 * Método que roda um código SQL no banco de dados configurado.
	 * @param sql
	 * @throws DAOException 
	 */
	public synchronized void sqlQuery(String sql) throws DAOException{
		try {
			SQLQuery q = getSession().createSQLQuery(sql);
			
			q.executeUpdate();
		} catch (HibernateException e) {
			throw new DAOException(e);
		}
	}
	
	/**
	 * Método para se fechar a session com o banco se esta está aberta
	 * @throws HibernateException
	 * @throws DAOException
	 */
	public void close(){
		try {
			if(getSession().isOpen())
				getSession().close();
		} catch (HibernateException e) {
			e.printStackTrace();
		} catch (DAOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Método que seta um mapa de parâmetros em uma query
	 * @param parametros
	 * @param q
	 */
	public void setParametros(Map<String, Object> parametros, Query q){
		for(Entry<String, Object> entry : parametros.entrySet()){
			q.setParameter(entry.getKey(), entry.getValue());
		}
	}
}
